/*******************************************************************************
 * Copyright (c) 2012-2017 Codenvy, S.A.
 * All rights reserved. This program and the accompanying materials
 * are made available under the terms of the Eclipse Public License v1.0
 * which accompanies this distribution, and is available at
 * http://www.eclipse.org/legal/epl-v10.html
 *
 * Contributors:
 *   Codenvy, S.A. - initial API and implementation
 *******************************************************************************/
package org.eclipse.che.plugin.maven.server.core.classpath;

import com.google.inject.Inject;
import com.google.inject.Singleton;
import com.google.inject.name.Named;

import org.eclipse.che.plugin.maven.server.MavenServerWrapper;
import org.eclipse.che.plugin.maven.server.MavenWrapperManager;
import org.eclipse.che.plugin.maven.server.core.MavenClasspathContainer;
import org.eclipse.che.plugin.maven.server.core.MavenProgressNotifier;
import org.eclipse.che.plugin.maven.server.core.MavenProjectManager;
import org.eclipse.che.plugin.maven.server.core.project.MavenProject;
import org.eclipse.che.maven.data.MavenArtifact;
import org.eclipse.che.maven.data.MavenArtifactKey;
import org.eclipse.che.maven.server.MavenTerminal;
import org.eclipse.core.runtime.NullProgressMonitor;
import org.eclipse.core.runtime.Path;
import org.eclipse.jdt.core.IClassFile;
import org.eclipse.jdt.core.IClasspathAttribute;
import org.eclipse.jdt.core.IClasspathContainer;
import org.eclipse.jdt.core.IClasspathEntry;
import org.eclipse.jdt.core.IJavaElement;
import org.eclipse.jdt.core.IJavaProject;
import org.eclipse.jdt.core.IPackageFragmentRoot;
import org.eclipse.jdt.core.IType;
import org.eclipse.jdt.core.JavaCore;
import org.eclipse.jdt.core.JavaModelException;
import org.eclipse.jdt.internal.core.JavaModelManager;
import org.slf4j.Logger;
import org.slf4j.LoggerFactory;

import java.io.File;
import java.util.Collections;
import java.util.List;

/**
 * @author Evgen Vidolob
 */
@Singleton
public class ClasspathManager {
    public static final String GROUP_ID_ATTRIBUTE    = "maven.groupId";
    public static final String ARTIFACT_ID_ATTRIBUTE = "maven.artifactId";
    public static final String VERSION_ATTRIBUTE     = "maven.version";
    public static final String CLASSIFIER_ATTRIBUTE  = "maven.classifier";
    public static final String PACKAGING_ATTRIBUTE   = "maven.packaging";
    public static final String SCOPE_ATTRIBUTE       = "maven.scope";

    private static final String SOURCES = "sources";

    private static final Logger LOG = LoggerFactory.getLogger(ClasspathManager.class);
    private final String                workspacePath;
    private final MavenWrapperManager   wrapperManager;
    private final MavenProjectManager   projectManager;
    private final MavenTerminal         terminal;
    private final MavenProgressNotifier notifier;
    private File                        localRepository;

    @Inject
    public ClasspathManager(@Named("che.user.workspaces.storage") String workspacePath,
                            MavenWrapperManager wrapperManager,
                            MavenProjectManager projectManager,
                            MavenTerminal terminal,
                            MavenProgressNotifier notifier) {

        this.workspacePath = workspacePath;
        this.wrapperManager = wrapperManager;
        this.projectManager = projectManager;
        this.terminal = terminal;
        this.notifier = notifier;
        MavenServerWrapper mavenServer = wrapperManager.getMavenServer(MavenWrapperManager.ServerType.DOWNLOAD);
        try {
            localRepository = mavenServer.getLocalRepository();
        }
         catch (RuntimeException e) {
             // We can got this exception if maven not install in system
             // This is temporary solution will be fix more accurate in https://jira.codenvycorp.com/browse/CHE-1120
            LOG.warn("Maven server not started looks like you don't have Maven in your path");
        } finally {
            wrapperManager.release(mavenServer);
        }
    }

    public void updateClasspath(MavenProject mavenProject) {
        IJavaProject javaProject = JavaCore.create(mavenProject.getProject());
        if (javaProject != null) {
            IClasspathEntry[] entries = getClasspath(mavenProject);
            MavenClasspathContainer container = new MavenClasspathContainer(entries);
            try {
                JavaCore.setClasspathContainer(new Path(MavenClasspathContainer.CONTAINER_ID),
                                               new IJavaProject[]{javaProject},
                                               new IClasspathContainer[]{container},
                                               new NullProgressMonitor());
            } catch (JavaModelException e) {
                LOG.error(e.getMessage(), e);
            }
        }
    }

    private IClasspathEntry[] getClasspath(MavenProject mavenProject) {
        ClasspathHelper helper = new ClasspathHelper(true);

        List<MavenArtifact> dependencies = mavenProject.getDependencies();
        for (MavenArtifact dependency : dependencies) {

            File file = dependency.getFile();
            if (file == null) {
                continue;
            }

            ClasspathEntryHelper entry;
            if (file.getPath().endsWith("pom.xml")) {
                String path = file.getParentFile().getPath();
                entry = helper.addProjectEntry(new Path(path.substring(workspacePath.length())));
            } else {
                entry = helper.addLibraryEntry(new Path(file.getPath()));
            }
            if (entry != null) {
                MavenArtifactKey artifactKey = new MavenArtifactKey(dependency.getGroupId(),
                                                                    dependency.getArtifactId(),
                                                                    dependency.getVersion(),
                                                                    dependency.getExtension(),
                                                                    dependency.getClassifier());
                entry.setArtifactKey(artifactKey);
                attachSources(entry);
            }

        }
        return helper.getEntries();
    }

    private void attachSources(ClasspathEntryHelper entry) {

        MavenArtifactKey artifactKey = entry.getArtifactKey();
        if (artifactKey != null) {
            File artifact = MavenLocalRepositoryUtil
                    .getFileForArtifact(localRepository, artifactKey.getGroupId(), artifactKey.getArtifactId(), artifactKey.getVersion(),
                                        SOURCES, artifactKey.getPackaging());
            if(artifact.exists()){
                entry.setSourcePath(new Path(artifact.getAbsolutePath()));
            }
        }
    }


    public boolean downloadSources(String projectPath, String fqn) {
        IJavaProject javaProject = JavaModelManager.getJavaModelManager().getJavaModel().getJavaProject(projectPath);
        try {
            IType type = javaProject.findType(fqn);
            if (type != null && type.isBinary()) {
                IClassFile classFile = type.getClassFile();
                if (classFile.getSourceRange() == null) {
                    IJavaElement element = classFile;
                    while (element.getParent() != null) {
                        element = element.getParent();
                        if (element instanceof IPackageFragmentRoot) {
                            IPackageFragmentRoot root = (IPackageFragmentRoot)element;

                            if (root.getSourceAttachmentPath() == null) {
                                return downloadSources(root);
                            }
                        }
                    }
                }
            }

        } catch (JavaModelException e) {
            LOG.error(e.getMessage(), e);
        }
        return false;
    }

    private boolean downloadSources(IPackageFragmentRoot fragmentRoot) throws JavaModelException {
        fragmentRoot.getAdapter(MavenArtifactKey.class);
        IClasspathEntry classpathEntry = fragmentRoot.getResolvedClasspathEntry();
        MavenArtifactKey artifactKey = getArtifactKey(classpathEntry);
        if (artifactKey != null) {
            MavenServerWrapper mavenServer = wrapperManager.getMavenServer(MavenWrapperManager.ServerType.DOWNLOAD);

            try {
                mavenServer.customize(projectManager.copyWorkspaceCache(), terminal, notifier, false, false);

                MavenArtifactKey sourceKey =
                        new MavenArtifactKey(artifactKey.getGroupId(), artifactKey.getArtifactId(), artifactKey.getVersion(),
                                             artifactKey.getPackaging(),
                                             SOURCES);
                MavenArtifact mavenArtifact = mavenServer.resolveArtifact(sourceKey, Collections.emptyList());
                if (mavenArtifact.isResolved()) {
                    updateClasspath(projectManager.findMavenProject(fragmentRoot.getJavaProject().getProject()));
                }
                return mavenArtifact.isResolved();
            } finally {
               wrapperManager.release(mavenServer);
            }
        }
        return false;
    }

    private MavenArtifactKey getArtifactKey(IClasspathEntry classpathEntry) {
        IClasspathAttribute[] attributes = classpathEntry.getExtraAttributes();
        String groupId = null;
        String artifactId = null;
        String version = null;
        String packaging = null;
        String classifier = null;
        for (IClasspathAttribute attribute : attributes) {
            if (ClasspathManager.GROUP_ID_ATTRIBUTE.equals(attribute.getName())) {
                groupId = attribute.getValue();
            } else if (ClasspathManager.ARTIFACT_ID_ATTRIBUTE.equals(attribute.getName())) {
                artifactId = attribute.getValue();
            } else if (ClasspathManager.VERSION_ATTRIBUTE.equals(attribute.getName())) {
                version = attribute.getValue();
            } else if (ClasspathManager.PACKAGING_ATTRIBUTE.equals(attribute.getName())) {
                packaging = attribute.getValue();
            } else if (ClasspathManager.CLASSIFIER_ATTRIBUTE.equals(attribute.getName())) {
                classifier = attribute.getValue();
            }
        }

        if (groupId != null && artifactId != null && version != null) {
            return new MavenArtifactKey(groupId, artifactId, version, packaging, classifier);
        }
        return null;
    }
}
